/***************************************************************************
 *   Copyright © 2010 by Nikolay Kornaev                                   *
 *   n.kornaev@gmail.com                                                   *
 *                                                                         *
 *                    This file is part of neuma.                          *
 *                                                                         *
 *   neuma is free software; you can redistribute it and/or modify it      *
 *   under the terms of the GNU General Public License as published by     *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   neuma is distributed in the hope that it will be useful,              *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, see <http://www.gnu.org/licenses/>   *
 ***************************************************************************/

#include <QtCore>
#include <QtGui>

#include "DisplayWidget.h"

namespace Neuma
{
  DisplayWidget::DisplayWidget(QWidget *parent) :
    QWidget(parent), printDialog(0), ownPrinter(false), initialized(false)
  {
  }

  void DisplayWidget::init(QPrinter *_printer)
  {
    if (_printer)
    {
      preview = new QPrintPreviewWidget(_printer, this);
      view = preview->findChild<QGraphicsView *> ();
      printer = _printer;
    }
    else
    {
      ownPrinter = true;
      printer = new QPrinter;
      preview = new QPrintPreviewWidget(printer, this);
    }
    connect(preview, SIGNAL(previewChanged()), SLOT(previewChanged()));
    setupActions();

    pageNumEdit = new LineEdit;
    pageNumEdit->setAlignment(Qt::AlignRight);
    pageNumEdit->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
    pageNumLabel = new QLabel;
    connect(pageNumEdit, SIGNAL(editingFinished()), SLOT(pageNumEdited()));

    zoomFactor = new QComboBox;
    zoomFactor->setEditable(true);
    zoomFactor->setMinimumContentsLength(7);
    zoomFactor->setInsertPolicy(QComboBox::NoInsert);
    LineEdit *zoomEditor = new LineEdit;
    zoomEditor->setValidator(new ZoomFactorValidator(1, 1000, 1, zoomEditor));
    zoomFactor->setLineEdit(zoomEditor);
    static const short factorsX2[] =
    {25, 50, 100, 200, 250, 300, 400, 800, 1600};
    for (int i = 0; i < int(sizeof(factorsX2) / sizeof(factorsX2[0])); ++i)
      zoomFactor->addItem(tr("%1%").arg(factorsX2[i] / 2.0));
    connect(zoomFactor->lineEdit(), SIGNAL(editingFinished()), SLOT(zoomFactorChanged()));
    connect(zoomFactor, SIGNAL(currentIndexChanged(int)), SLOT(zoomFactorChanged()));

    PrintPreviewMainWindow *mw = new PrintPreviewMainWindow(this);
    QToolBar *toolbar = new QToolBar(mw);
    toolbar->addAction(fitWidthAction);
    toolbar->addAction(fitPageAction);
    toolbar->addSeparator();
    toolbar->addWidget(zoomFactor);
    toolbar->addAction(zoomOutAction);
    toolbar->addAction(zoomInAction);
    toolbar->addSeparator();
    toolbar->addAction(portraitAction);
    toolbar->addAction(landscapeAction);
    toolbar->addSeparator();
    toolbar->addAction(firstPageAction);
    toolbar->addAction(prevPageAction);

    // this is to ensure the label text and the editor text are
    // aligned in all styles - the extra QVBoxLayout is a workaround
    // for bug in QFormLayout
    QWidget *pageEdit = new QWidget(toolbar);
    QVBoxLayout *vboxLayout = new QVBoxLayout;
    vboxLayout->setContentsMargins(0, 0, 0, 0);
#ifdef Q_WS_MAC
    // We query the widgets about their size and then we fix the size.
    // This should do the trick for the laying out part...
    QSize pageNumEditSize, pageNumLabelSize;
    pageNumEditSize = pageNumEdit->minimumSizeHint();
    pageNumLabelSize = pageNumLabel->minimumSizeHint();
    pageNumEdit->resize(pageNumEditSize);
    pageNumLabel->resize(pageNumLabelSize);
#endif
    QFormLayout *formLayout = new QFormLayout;
#ifdef Q_WS_MAC
    // We have to change the growth policy in Mac.
    formLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
#endif
    formLayout->setWidget(0, QFormLayout::LabelRole, pageNumEdit);
    formLayout->setWidget(0, QFormLayout::FieldRole, pageNumLabel);
    vboxLayout->addLayout(formLayout);
    vboxLayout->setAlignment(Qt::AlignVCenter);
    pageEdit->setLayout(vboxLayout);
    toolbar->addWidget(pageEdit);

    toolbar->addAction(nextPageAction);
    toolbar->addAction(lastPageAction);
    toolbar->addSeparator();
    toolbar->addAction(singleModeAction);
    toolbar->addAction(facingModeAction);
    toolbar->addAction(overviewModeAction);
    toolbar->addSeparator();
    toolbar->addAction(pageSetupAction);
    toolbar->addAction(printAction);
#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA)
    toolbar->addAction(closeAction);
#endif

    // Cannot use the actions' triggered signal here, since it doesn't autorepeat
    QToolButton *zoomInButton = static_cast<QToolButton *> (toolbar->widgetForAction(zoomInAction));
    QToolButton *zoomOutButton =
        static_cast<QToolButton *> (toolbar->widgetForAction(zoomOutAction));
    zoomInButton->setAutoRepeat(true);
    zoomInButton->setAutoRepeatInterval(200);
    zoomInButton->setAutoRepeatDelay(200);
    zoomOutButton->setAutoRepeat(true);
    zoomOutButton->setAutoRepeatInterval(200);
    zoomOutButton->setAutoRepeatDelay(200);
    connect(zoomInButton, SIGNAL(clicked()), SLOT(zoomIn()));
    connect(zoomOutButton, SIGNAL(clicked()), SLOT(zoomOut()));

    mw->addToolBar(toolbar);
    mw->setCentralWidget(preview);
    // QMainWindows are always created as top levels, force it to be a
    // plain widget
    mw->setParent(this, Qt::Widget);

    QVBoxLayout *topLayout = new QVBoxLayout;
    topLayout->addWidget(mw);
    topLayout->setMargin(0);
    setLayout(topLayout);

    QString caption = tr("Print Preview");
    if (!printer->docName().isEmpty())
      caption += QString::fromLatin1(": ") + printer->docName();
    setWindowTitle(caption);

    if (!printer->isValid()
#if defined(Q_WS_WIN) || defined(Q_WS_MAC)
      || printer->outputFormat() != QPrinter::NativeFormat
#endif
      )
      pageSetupAction->setEnabled(false);
    preview->setFocus();
  }

  void DisplayWidget::setupActions()
  {
    // Navigation
    navGroup = new QActionGroup(this);
    navGroup->setExclusive(false);
    nextPageAction = navGroup->addAction(tr("Next page"));
    prevPageAction = navGroup->addAction(tr("Previous page"));
    firstPageAction = navGroup->addAction(tr("First page"));
    lastPageAction = navGroup->addAction(tr("Last page"));
    qt_setupActionIcon(nextPageAction, QLatin1String("go-next"));
    qt_setupActionIcon(prevPageAction, QLatin1String("go-previous"));
    qt_setupActionIcon(firstPageAction, QLatin1String("go-first"));
    qt_setupActionIcon(lastPageAction, QLatin1String("go-last"));
    connect(navGroup, SIGNAL(triggered(QAction*)), SLOT(navigate(QAction*)));

    fitGroup = new QActionGroup(this);
    fitWidthAction = fitGroup->addAction(tr("Fit width"));
    fitPageAction = fitGroup->addAction(tr("Fit page"));
    fitWidthAction->setObjectName(QLatin1String("fitWidthAction"));
    fitPageAction->setObjectName(QLatin1String("fitPageAction"));
    fitWidthAction->setCheckable(true);
    fitPageAction->setCheckable(true);
    qt_setupActionIcon(fitWidthAction, QLatin1String("fit-width"));
    qt_setupActionIcon(fitPageAction, QLatin1String("fit-page"));
    connect(fitGroup, SIGNAL(triggered(QAction*)), SLOT(fit(QAction*)));

    // Zoom
    zoomGroup = new QActionGroup(this);
    zoomInAction = zoomGroup->addAction(tr("Zoom in"));
    zoomOutAction = zoomGroup->addAction(tr("Zoom out"));
    qt_setupActionIcon(zoomInAction, QLatin1String("zoom-in"));
    qt_setupActionIcon(zoomOutAction, QLatin1String("zoom-out"));

    // Portrait/Landscape
    orientationGroup = new QActionGroup(this);
    portraitAction = orientationGroup->addAction(tr("Portrait"));
    landscapeAction = orientationGroup->addAction(tr("Landscape"));
    portraitAction->setCheckable(true);
    landscapeAction->setCheckable(true);
    qt_setupActionIcon(portraitAction, QLatin1String("layout-portrait"));
    qt_setupActionIcon(landscapeAction, QLatin1String("layout-landscape"));
    connect(portraitAction, SIGNAL(triggered(bool)), preview, SLOT(setPortraitOrientation()));
    connect(landscapeAction, SIGNAL(triggered(bool)), preview, SLOT(setLandscapeOrientation()));

    // Display mode
    modeGroup = new QActionGroup(this);
    singleModeAction = modeGroup->addAction(tr("Show single page"));
    facingModeAction = modeGroup->addAction(tr("Show facing pages"));
    overviewModeAction = modeGroup->addAction(tr("Show overview of all pages"));
    qt_setupActionIcon(singleModeAction, QLatin1String("view-page-one"));
    qt_setupActionIcon(facingModeAction, QLatin1String("view-page-sided"));
    qt_setupActionIcon(overviewModeAction, QLatin1String("view-page-multi"));
    singleModeAction->setObjectName(QLatin1String("singleModeAction"));
    facingModeAction->setObjectName(QLatin1String("facingModeAction"));
    overviewModeAction->setObjectName(QLatin1String("overviewModeAction"));

    singleModeAction->setCheckable(true);
    facingModeAction->setCheckable(true);
    overviewModeAction->setCheckable(true);
    connect(modeGroup, SIGNAL(triggered(QAction*)), SLOT(setMode(QAction*)));

    // Print
    printerGroup = new QActionGroup(this);
    printAction = printerGroup->addAction(tr("Print"));
    pageSetupAction = printerGroup->addAction(tr("Page setup"));
    qt_setupActionIcon(printAction, QLatin1String("print"));
    qt_setupActionIcon(pageSetupAction, QLatin1String("page-setup"));
    connect(printAction, SIGNAL(triggered(bool)), SLOT(print()));
    connect(pageSetupAction, SIGNAL(triggered(bool)), SLOT(pageSetup()));
#if defined(Q_WS_MAC) && !defined(QT_MAC_USE_COCOA)
    closeAction = printerGroup->addAction(QCoreApplication::translate("QPrintPreviewDialog", "Close"));
    QObject::connect(closeAction, SIGNAL(triggered(bool)), q, SLOT(reject()));
#endif

    // Initial state:
    fitPageAction->setChecked(true);
    singleModeAction->setChecked(true);
    if (preview->orientation() == QPrinter::Portrait)
      portraitAction->setChecked(true);
    else landscapeAction->setChecked(true);
  }

  bool DisplayWidget::isFitting()
  {
    return (fitGroup->isExclusive() && (fitWidthAction->isChecked() || fitPageAction->isChecked()));
  }

  void DisplayWidget::setFitting(bool on)
  {
    if (isFitting() == on)
      return;
    fitGroup->setExclusive(on);
    if (on)
    {
      QAction* action = fitWidthAction->isChecked() ? fitWidthAction : fitPageAction;
      action->setChecked(true);
      if (fitGroup->checkedAction() != action)
      {
        // work around exclusitivity problem
        fitGroup->removeAction(action);
        fitGroup->addAction(action);
      }
    }
    else
    {
      fitWidthAction->setChecked(false);
      fitPageAction->setChecked(false);
    }
  }

  void DisplayWidget::updateNavActions()
  {
    int curPage = preview->currentPage();
    int numPages = preview->pageCount();
    nextPageAction->setEnabled(curPage < numPages);
    prevPageAction->setEnabled(curPage > 1);
    firstPageAction->setEnabled(curPage > 1);
    lastPageAction->setEnabled(curPage < numPages);
    pageNumEdit->setText(QString::number(curPage));
  }

  void DisplayWidget::updatePageNumLabel()
  {
    int numPages = preview->pageCount();
    int maxChars = QString::number(numPages).length();
    pageNumLabel->setText(QString::fromLatin1("/ %1").arg(numPages));
    int cyphersWidth = fontMetrics().width(QString().fill(QLatin1Char('8'), maxChars));
    int maxWidth = pageNumEdit->minimumSizeHint().width() + cyphersWidth;
    pageNumEdit->setMinimumWidth(maxWidth);
    pageNumEdit->setMaximumWidth(maxWidth);
    pageNumEdit->setValidator(new QIntValidator(1, numPages, pageNumEdit));
    // any old one will be deleted later along with its parent pageNumEdit
  }

  void DisplayWidget::updateZoomFactor()
  {
    zoomFactor->lineEdit()->setText(QString().sprintf("%.1f%%", preview->zoomFactor() * 100));
  }

  void DisplayWidget::fit(QAction* action)
  {
    setFitting(true);
    if (action == fitPageAction)
      preview->fitInView();
    else preview->fitToWidth();
  }

  void DisplayWidget::zoomIn()
  {
    setFitting(false);
    preview->zoomIn();
    updateZoomFactor();
  }

  void DisplayWidget::zoomOut()
  {
    setFitting(false);
    preview->zoomOut();
    updateZoomFactor();
  }

  void DisplayWidget::pageNumEdited()
  {
    bool ok = false;
    int res = pageNumEdit->text().toInt(&ok);
    if (ok)
      preview->setCurrentPage(res);
  }

  void DisplayWidget::navigate(QAction* action)
  {
    int curPage = preview->currentPage();
    if (action == prevPageAction)
      preview->setCurrentPage(curPage - 1);
    else
      if (action == nextPageAction)
        preview->setCurrentPage(curPage + 1);
      else
        if (action == firstPageAction)
          preview->setCurrentPage(1);
        else
          if (action == lastPageAction)
            preview->setCurrentPage(preview->pageCount());
    updateNavActions();
  }

  void DisplayWidget::setMode(QAction* action)
  {
    if (action == overviewModeAction)
    {
      preview->setViewMode(QPrintPreviewWidget::AllPagesView);
      setFitting(false);
      fitGroup->setEnabled(false);
      navGroup->setEnabled(false);
      pageNumEdit->setEnabled(false);
      pageNumLabel->setEnabled(false);
    }
    else
      if (action == facingModeAction)
      {
        preview->setViewMode(QPrintPreviewWidget::FacingPagesView);
      }
      else
      {
        preview->setViewMode(QPrintPreviewWidget::SinglePageView);
      }
    if (action == facingModeAction || action == singleModeAction)
    {
      fitGroup->setEnabled(true);
      navGroup->setEnabled(true);
      pageNumEdit->setEnabled(true);
      pageNumLabel->setEnabled(true);
      setFitting(true);
    }
  }

  void DisplayWidget::pageSetup()
  {
    QPageSetupDialog pageSetup(printer, this);
    if (pageSetup.exec() == QDialog::Accepted)
    {
      // update possible orientation changes
      if (preview->orientation() == QPrinter::Portrait)
      {
        portraitAction->setChecked(true);
        preview->setPortraitOrientation();
      }
      else
      {
        landscapeAction->setChecked(true);
        preview->setLandscapeOrientation();
      }
    }
  }

  void DisplayWidget::print()
  {
    if (printer->outputFormat() != QPrinter::NativeFormat)
    {
      QString title;
      QString suffix;
      if (printer->outputFormat() == QPrinter::PdfFormat)
      {
        title = tr("Export to PDF");
        suffix = QLatin1String(".pdf");
      }
      else
      {
        title = tr("Export to PostScript");
        suffix = QLatin1String(".ps");
      }
      QString fileName = QFileDialog::getSaveFileName(this, title, printer->outputFileName(),
          QLatin1Char('*') + suffix);
      if (!fileName.isEmpty())
      {
        if (QFileInfo(fileName).suffix().isEmpty())
          fileName.append(suffix);
        printer->setOutputFileName(fileName);
      }
      if (!printer->outputFileName().isEmpty())
        preview->print();
      return;
    }

    if (!printDialog)
      printDialog = new QPrintDialog(printer, this);
    if (printDialog->exec() == QDialog::Accepted)
      preview->print();
  }

  void DisplayWidget::previewChanged()
  {
    updateNavActions();
    updatePageNumLabel();
    updateZoomFactor();
  }

  void DisplayWidget::zoomFactorChanged()
  {
    QString text = zoomFactor->lineEdit()->text();
    bool ok;
    qreal factor = text.remove(QLatin1Char('%')).toFloat(&ok);
    factor = qMax(qreal(1.0), qMin(qreal(1000.0), factor));
    if (ok)
    {
      preview->setZoomFactor(factor / 100.0);
      zoomFactor->setEditText(QString::fromLatin1("%1%").arg(factor));
      setFitting(false);
    }
  }
}
